/*
 Copyright 2009-2010 AdMob, Inc.

    Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

package com.adwhirl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Random;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationManager;
import android.provider.Settings.Secure;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;

import com.adwhirl.obj.Custom;
import com.adwhirl.obj.Extra;
import com.adwhirl.obj.Ration;
import com.adwhirl.util.AdWhirlUtil;

public class AdWhirlManager {
	public String keyAdWhirl;

	private Extra extra;
	private List<Ration> rationsList;
	private double totalWeight = 0;
	private final WeakReference<Context> contextReference;

	// Default config expire timeout is 30 minutes.
	private static long configExpireTimeout = 1800000;

	Iterator<Ration> rollovers;

	public String localeString;
	public String deviceIDHash;

	public Location location;

	private final static String PREFS_STRING_TIMESTAMP = "timestamp";
	private final static String PREFS_STRING_CONFIG = "config";

	public AdWhirlManager(final WeakReference<Context> contextReference,
			final String keyAdWhirl) {
		Log.i(AdWhirlUtil.ADWHIRL, "Creating adWhirlManager...");
		this.contextReference = contextReference;
		this.keyAdWhirl = keyAdWhirl;

		localeString = Locale.getDefault().toString();
		Log.d(AdWhirlUtil.ADWHIRL, "Locale is: " + localeString);

		MessageDigest md;
		try {
			md = MessageDigest.getInstance("MD5");
			final StringBuffer deviceIDString = new StringBuffer(
					Secure.ANDROID_ID);
			deviceIDString.append("AdWhirl");
			deviceIDHash = AdWhirlUtil.convertToHex(md.digest(deviceIDString
					.toString().getBytes()));
		} catch (final NoSuchAlgorithmException e) {
			deviceIDHash = "00000000000000000000000000000000";
		}
		Log.d(AdWhirlUtil.ADWHIRL, "Hashed device ID is: " + deviceIDHash);

		Log.i(AdWhirlUtil.ADWHIRL, "Finished creating adWhirlManager");
	}

	public static void setConfigExpireTimeout(final long configExpireTimeout) {
		AdWhirlManager.configExpireTimeout = configExpireTimeout;
	}

	public Extra getExtra() {
		if (totalWeight <= 0) {
			Log.i(AdWhirlUtil.ADWHIRL,
					"Sum of ration weights is 0 - no ads to be shown");
			return null;
		} else {
			return this.extra;
		}
	}

	public Ration getRation() {
		final Random random = new Random();

		final double r = random.nextDouble() * totalWeight;
		double s = 0;

		Log.d(AdWhirlUtil.ADWHIRL, "Dart is <" + r + "> of <" + totalWeight
				+ ">");

		final Iterator<Ration> it = this.rationsList.iterator();
		Ration ration = null;
		while (it.hasNext()) {
			ration = it.next();
			s += ration.weight;

			if (s >= r) {
				break;
			}
		}

		return ration;
	}

	public Ration getRollover() {
		if (this.rollovers == null) {
			return null;
		}

		Ration ration = null;
		if (this.rollovers.hasNext()) {
			ration = this.rollovers.next();
		}

		return ration;
	}

	public void resetRollover() {
		this.rollovers = this.rationsList.iterator();
	}

	public Custom getCustom(final String nid) {
		final HttpClient httpClient = new DefaultHttpClient();

		// IC: commented
		// String locationString;
		// if (extra.locationOn == 1) {
		// location = getLocation();
		// if (location != null) {
		// locationString = String.format(Locale.ENGLISH,
		// AdWhirlUtil.locationString, location.getLatitude(),
		// location.getLongitude(), location.getTime());
		// } else {
		// locationString = "";
		// }
		// } else {
		// location = null;
		// locationString = "";
		// }

		// TODO: IC: removed params except nid
		// final String url = String.format(Locale.ENGLISH,
		// AdWhirlUtil.urlCustom,
		// this.keyAdWhirl, nid, deviceIDHash, localeString,
		// locationString, AdWhirlUtil.VERSION);
		final String url = String.format(Locale.ENGLISH, AdWhirlUtil.urlCustom,
				AdWhirlUtil.VERSION, nid);
		final HttpGet httpGet = new HttpGet(url);

		HttpResponse httpResponse;
		try {
			httpResponse = httpClient.execute(httpGet);

			Log.d(AdWhirlUtil.ADWHIRL, httpResponse.getStatusLine().toString());

			final HttpEntity entity = httpResponse.getEntity();

			if (entity != null) {
				final InputStream inputStream = entity.getContent();
				final String jsonString = convertStreamToString(inputStream);
				return parseCustomJsonString(jsonString);
			}
		} catch (final ClientProtocolException e) {
			Log.e(AdWhirlUtil.ADWHIRL,
					"Caught ClientProtocolException in getCustom()", e);
		} catch (final IOException e) {
			Log.e(AdWhirlUtil.ADWHIRL, "Caught IOException in getCustom()", e);
		}

		return null;
	}

	public void fetchConfig() {
		final Context context = contextReference.get();

		// If the context is null here something went wrong with initialization.
		if (context == null) {
			return;
		}

		final SharedPreferences adWhirlPrefs = context.getSharedPreferences(
				keyAdWhirl, Context.MODE_PRIVATE);
		String jsonString = adWhirlPrefs.getString(PREFS_STRING_CONFIG, null);
		final long timestamp = adWhirlPrefs.getLong(PREFS_STRING_TIMESTAMP, -1);

		Log.d(AdWhirlUtil.ADWHIRL, "Prefs{" + keyAdWhirl + "}: {\""
				+ PREFS_STRING_CONFIG + "\": \"" + jsonString + "\", \""
				+ PREFS_STRING_TIMESTAMP + "\": " + timestamp + "}");

		// IC added backup
		final String oldJsonString = jsonString;
		// IC added empty
		if (jsonString == null
				|| "".equals(jsonString)
				|| configExpireTimeout == -1
				|| System.currentTimeMillis() >= timestamp
						+ configExpireTimeout) {
			Log.i(AdWhirlUtil.ADWHIRL,
					"Stored config info not present or expired, fetching fresh data");

			final HttpClient httpClient = new DefaultHttpClient();

			// TODO: IC: removed version
			// final String url = String
			// .format(Locale.ENGLISH, AdWhirlUtil.urlConfig,
			// this.keyAdWhirl, AdWhirlUtil.VERSION);

			final String url = String
					.format(Locale.ENGLISH, AdWhirlUtil.urlConfig,
							AdWhirlUtil.VERSION, this.keyAdWhirl);

			final HttpGet httpGet = new HttpGet(url);

			HttpResponse httpResponse;
			try {
				httpResponse = httpClient.execute(httpGet);

				Log.d(AdWhirlUtil.ADWHIRL, httpResponse.getStatusLine()
						.toString());

				final HttpEntity entity = httpResponse.getEntity();

				if (entity != null) {
					final InputStream inputStream = entity.getContent();
					jsonString = convertStreamToString(inputStream);
				}
			} catch (final ClientProtocolException e) {
				Log.e(AdWhirlUtil.ADWHIRL,
						"Caught ClientProtocolException in fetchConfig()", e);
			} catch (final IOException e) {
				Log.e(AdWhirlUtil.ADWHIRL,
						"Caught IOException in fetchConfig()", e);
			}
		} else {
			Log.i(AdWhirlUtil.ADWHIRL, "Using stored config data");
		}

		// IC parsing with fallback
		Throwable error = null;
		try {
			parseConfigurationString(jsonString);
		} catch (final Throwable e) {
			if ((oldJsonString != null && !oldJsonString.equals(jsonString))) {
				Log.e(AdWhirlUtil.ADWHIRL,
						"Unable to parse response from JSON. fallback.", e);
				try {
					parseConfigurationString(oldJsonString);
				} catch (final Throwable e1) {
					error = e1;
				}
			} else {
				// error
				error = e;

			}
		}
		// check has it error
		if (error != null) {
			// error
			this.extra = new Extra();
			Log.e(AdWhirlUtil.ADWHIRL,
					"Unable to parse response from JSON. This may be fatal.",
					error);
		} else {
			// parse ok - save it
			try {
				final SharedPreferences.Editor editor = adWhirlPrefs.edit();
				editor.putString(PREFS_STRING_CONFIG, jsonString);
				editor.putLong(PREFS_STRING_TIMESTAMP,
						System.currentTimeMillis());
				editor.commit();
			} catch (final Throwable e2) {
				Log.e(AdWhirlUtil.ADWHIRL, "error during prefs save", e2);
			}

		}
	}

	private String convertStreamToString(final InputStream is) {
		final BufferedReader reader = new BufferedReader(new InputStreamReader(
				is), 8192);
		final StringBuilder sb = new StringBuilder();

		String line = null;
		try {
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
		} catch (final IOException e) {
			Log.e(AdWhirlUtil.ADWHIRL,
					"Caught IOException in convertStreamToString()", e);
			return null;
		} finally {
			try {
				is.close();
			} catch (final IOException e) {
				Log.e(AdWhirlUtil.ADWHIRL,
						"Caught IOException in convertStreamToString()", e);
				return null;
			}
		}

		return sb.toString();
	}

	private void parseConfigurationString(final String jsonString)
			throws JSONException {
		Log.d(AdWhirlUtil.ADWHIRL, "Received jsonString: " + jsonString);
		final JSONObject json = new JSONObject(jsonString);
		parseExtraJson(json.getJSONObject("extra"));
		parseRationsJson(json.getJSONArray("rations"));
	}

	private void parseExtraJson(final JSONObject json) {
		final Extra extra = new Extra();

		try {
			extra.cycleTime = json.getInt("cycle_time");
			extra.locationOn = json.getInt("location_on");
			extra.transition = json.getInt("transition");

			// Due to legacy clients, the server reports alpha on a scale of
			// 0.0-1.0
			// instead of 0-255

			final JSONObject backgroundColor = json
					.getJSONObject("background_color_rgb");
			extra.bgRed = backgroundColor.getInt("red");
			extra.bgGreen = backgroundColor.getInt("green");
			extra.bgBlue = backgroundColor.getInt("blue");
			extra.bgAlpha = backgroundColor.getInt("alpha") * 255;

			final JSONObject textColor = json.getJSONObject("text_color_rgb");
			extra.fgRed = textColor.getInt("red");
			extra.fgGreen = textColor.getInt("green");
			extra.fgBlue = textColor.getInt("blue");
			extra.fgAlpha = textColor.getInt("alpha") * 255;
		} catch (final JSONException e) {
			Log.e(AdWhirlUtil.ADWHIRL,
					"Exception in parsing config.extra JSON. This may or may not be fatal.",
					e);
		}

		this.extra = extra;
	}

	private void parseRationsJson(final JSONArray json) {
		final List<Ration> rationsList = new ArrayList<Ration>();

		this.totalWeight = 0;

		try {
			int i;
			for (i = 0; i < json.length(); i++) {
				final JSONObject jsonRation = json.getJSONObject(i);
				if (jsonRation == null) {
					continue;
				}

				final Ration ration = new Ration();

				ration.nid = jsonRation.getString("nid");
				ration.type = jsonRation.getInt("type");
				ration.name = jsonRation.getString("nname");
				ration.weight = jsonRation.getInt("weight");
				ration.priority = jsonRation.getInt("priority");

				// Quattro has a special key format due to legacy compatibility.
				switch (ration.type) {
				case AdWhirlUtil.NETWORK_TYPE_QUATTRO:
					JSONObject keyObj = jsonRation.getJSONObject("key");
					ration.key = keyObj.getString("siteID");
					ration.key2 = keyObj.getString("publisherID");
					break;

				case AdWhirlUtil.NETWORK_TYPE_NEXAGE:
					keyObj = jsonRation.getJSONObject("key");
					ration.key = keyObj.getString("dcn");
					ration.key2 = keyObj.getString("position");
					break;

				default:
					ration.key = jsonRation.getString("key");
					break;
				}

				this.totalWeight += ration.weight;

				rationsList.add(ration);
			}
		} catch (final JSONException e) {
			Log.e(AdWhirlUtil.ADWHIRL,
					"JSONException in parsing config.rations JSON. This may or may not be fatal.",
					e);
		}

		Collections.sort(rationsList);

		this.rationsList = rationsList;
		this.rollovers = this.rationsList.iterator();
	}

	private Custom parseCustomJsonString(final String jsonString) {
		Log.d(AdWhirlUtil.ADWHIRL, "Received custom jsonString: " + jsonString);

		final Custom custom = new Custom();
		try {
			final JSONObject json = new JSONObject(jsonString);

			custom.type = json.getInt("ad_type");
			custom.imageLink = json.getString("img_url");
			custom.link = json.getString("redirect_url");
			custom.description = json.getString("ad_text");

			// Populate high-res house ad info if available
			// TODO(wesgoodman): remove try/catch block after upgrading server
			// to
			// new protocol.
			try {
				custom.imageLink480x75 = json.getString("img_url_480x75");
			} catch (final JSONException e) {
				custom.imageLink480x75 = null;
			}

			final DisplayMetrics metrics = new DisplayMetrics();
			((WindowManager) contextReference.get().getSystemService(
					Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(
					metrics);
			if (metrics.density == 1.5
					&& custom.type == AdWhirlUtil.CUSTOM_TYPE_BANNER
					&& custom.imageLink480x75 != null
					&& custom.imageLink480x75.length() != 0) {
				custom.image = fetchImage(custom.imageLink480x75);
			} else {
				custom.image = fetchImage(custom.imageLink);
			}
		} catch (final JSONException e) {
			Log.e(AdWhirlUtil.ADWHIRL,
					"Caught JSONException in parseCustomJsonString()", e);
			return null;
		}

		return custom;
	}

	private Drawable fetchImage(final String urlString) {
		try {
			final URL url = new URL(urlString);
			final InputStream is = (InputStream) url.getContent();
			final Drawable d = Drawable.createFromStream(is, "src");
			return d;
		} catch (final Exception e) {
			Log.e(AdWhirlUtil.ADWHIRL, "Unable to fetchImage(): ", e);
			return null;
		}
	}

	public Location getLocation() {
		if (contextReference == null) {
			return null;
		}

		final Context context = contextReference.get();
		if (context == null) {
			return null;
		}

		Location location = null;

		if (context
				.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
			final LocationManager lm = (LocationManager) context
					.getSystemService(Context.LOCATION_SERVICE);
			location = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
		} else if (context
				.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
			final LocationManager lm = (LocationManager) context
					.getSystemService(Context.LOCATION_SERVICE);
			location = lm
					.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
		}
		return location;
	}
}
